package com.atguigu.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.common.to.es.SkuEsModel;
import com.atguigu.common.tool.utils.Func;
import com.atguigu.common.tool.utils.IntegerPool;
import com.atguigu.common.tool.utils.StringPool;
import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import com.atguigu.gulimall.search.constant.EsConstant;
import com.atguigu.gulimall.search.dto.SearchParamDTO;
import com.atguigu.gulimall.search.service.IMallSearchService;
import com.atguigu.gulimall.search.vo.SearchResultVO;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author : wenchao.long
 * @date : Created in 2021/8/2 19:33 周一
 * @description :
 */
@Service
@AllArgsConstructor
@Slf4j
public class MallSearchServiceImpl implements IMallSearchService {

    private final RestHighLevelClient client;

    @Override
    public SearchResultVO search(SearchParamDTO searchParamDTO) {

        SearchResultVO searchResult = null;

        // 1. 准备检索请求
        SearchRequest searchRequest = buildSearchRequest(searchParamDTO);

        try {
            SearchResponse response = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
            searchResult = buildSearchResult(response, searchParamDTO);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return searchResult;
    }

    /**
     * 构建结果数据
     *
     * @param response response
     * @return SearchResultVO
     */
    private SearchResultVO buildSearchResult(SearchResponse response,
                                             SearchParamDTO searchParam) {
        SearchResultVO result = new SearchResultVO();
        SearchHits hits = response.getHits();
        // 1. 返回所有查询到的产品
        if (Func.isNotEmpty(hits.getHits())) {
            List<SkuEsModel> skuEsModels = Lists.newArrayList();
            for (SearchHit hit : hits) {
                String sourceAsString = hit.getSourceAsString();
                SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
                //设置高亮属性
                if (Func.isNotEmpty(searchParam.getKeyword())) {
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    String highLight = skuTitle.getFragments()[0].string();
                    skuEsModel.setSkuTitle(highLight);
                }
                skuEsModels.add(skuEsModel);
            }
            result.setProduct(skuEsModels);
        }

        Aggregations aggregations = response.getAggregations();

        // 2. 当前所有商品涉及到的所有属性信息
        ParsedNested attrAgg = aggregations.get("attr_agg");
        Aggregations aggAggregations = attrAgg.getAggregations();
        ParsedLongTerms attrIdAgg = aggAggregations.get("attr_id_agg");
        List<SearchResultVO.AttrVo> attrVoList = attrIdAgg.getBuckets()
                .stream()
                .map(item -> {
                    long attrId = item.getKeyAsNumber().longValue();
                    Aggregations itemAggregations = item.getAggregations();

                    ParsedStringTerms attrNameAgg = itemAggregations.get("attr_name_agg");
                    String attrName = attrNameAgg.getBuckets()
                            .stream()
                            .map(MultiBucketsAggregation.Bucket::getKeyAsString)
                            .collect(Collectors.joining(","));

                    ParsedStringTerms attrValueAgg = itemAggregations.get("attr_value_agg");
                    List<String> attrValues = attrValueAgg.getBuckets()
                            .stream()
                            .map(MultiBucketsAggregation.Bucket::getKeyAsString)
                            .collect(Collectors.toList());

                    return SearchResultVO.AttrVo.builder()
                            .attrId(attrId)
                            .attrName(attrName)
                            .attrValue(attrValues)
                            .build();
                })
                .collect(Collectors.toList());

        result.setAttrs(attrVoList);

        // 3. 当前所有商品涉及到的所有品牌信息
        ParsedLongTerms brandAgg = aggregations.get("brand_agg");
        List<SearchResultVO.BrandVo> brandVos = brandAgg.getBuckets()
                .stream()
                .map(item -> {
                    Aggregations itemAggregations = item.getAggregations();

                    Terms brandNameAgg = itemAggregations.get("brand_name_agg");
                    String brandName = brandNameAgg.getBuckets()
                            .stream()
                            .map(MultiBucketsAggregation.Bucket::getKeyAsString)
                            .collect(Collectors.joining(","));

                    Terms brandImgAgg = itemAggregations.get("brand_img_agg");
                    String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();

                    return SearchResultVO.BrandVo.builder()
                            .brandId(item.getKeyAsNumber().longValue())
                            .brandImg(brandName)
                            .brandImg(brandImg)
                            .build();
                })
                .collect(Collectors.toList());
        result.setBrands(brandVos);

        // 4. 当前所有商品涉及到的所有分类信息
        ParsedLongTerms catelogAgg = aggregations.get("catelog_agg");
        List<SearchResultVO.CatalogVo> catalogVoList = catelogAgg.getBuckets()
                .stream()
                .map(item -> {
                    Aggregations itemAggregations = item.getAggregations();

                    Terms catalogNameAgg = itemAggregations.get("catalog_name_agg");
                    String catalogName = catalogNameAgg.getBuckets()
                            .stream()
                            .map(MultiBucketsAggregation.Bucket::getKeyAsString)
                            .collect(Collectors.joining(","));
                    return SearchResultVO.CatalogVo.builder()
                            .catalogId(item.getKeyAsNumber().longValue())
                            .catalogName(catalogName)
                            .build();
                })
                .collect(Collectors.toList());

        result.setCatalogs(catalogVoList);

        // 5. 分页信息 - 页码
        result.setPageNum(searchParam.getPageNum());
        long total = hits.getTotalHits().value;
        // 5. 分页信息 - 总记录数
        // 5. 分页信息 - 总页码
        int totalPage = (int) ((total - 1) / EsConstant.PRODUCT_PAGE_SIZE + 1);
        result.setTotalPages(totalPage);

        List<Integer> pageNavs = Stream.iterate(1, item -> item + 1).limit(totalPage)
                .collect(Collectors.toList());
        result.setPageNavs(pageNavs);


        return result;
    }

    /**
     * 准备检索请求
     * 模糊匹配 过滤(按照属性，分类，品牌，价格区间，库存)
     * 排序，分页，高亮，聚合分析
     *
     * @return SearchRequest
     */
    private SearchRequest buildSearchRequest(SearchParamDTO searchParam) {
        // 构建DSL语句
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        /*
        1. 模糊匹配 过滤(按照属性，分类，品牌，价格区间，库存)
         */
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();

        // 1.1 must匹配
        String keyword = searchParam.getKeyword();
        if (Func.isNotEmpty(keyword)) {
            queryBuilder.must(QueryBuilders.matchQuery("skuTitle", keyword));
        }

        // 1.2 filter-catalogId
        Long catalog3Id = searchParam.getCatalog3Id();
        if (Func.isNotEmpty(catalog3Id)) {
            queryBuilder.filter(QueryBuilders.termQuery("catalogId", catalog3Id));
        }
        // 1.2 filter-brandId
        List<Long> brandIds = searchParam.getBrandId();
        if (Func.isNotEmpty(brandIds)) {
            queryBuilder.filter(QueryBuilders.termsQuery("brandId", brandIds));
        }
        // 1.3 filter-hasStock
        Integer hasStock = searchParam.getHasStock();
        queryBuilder.filter(QueryBuilders.termsQuery("hasStock", IntegerPool.ONE.equals(hasStock)));

        // 1.4 filter-skuPrice 1_500 / _500 / 500_
        String skuPrice = searchParam.getSkuPrice();
        if (Func.isNotEmpty(skuPrice)) {
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            String[] s = skuPrice.split(StringPool.UNDERSCORE);
            if (s.length == IntegerPool.TWO) {
                rangeQuery.gte(s[0]).lte(s[1]);
            } else {
                if (skuPrice.startsWith(StringPool.UNDERSCORE)) {
                    rangeQuery.lte(s[0]);
                }
                if (skuPrice.endsWith(StringPool.UNDERSCORE)) {
                    rangeQuery.gte(s[0]);
                }
            }
            queryBuilder.filter();
        }

        // 1.5 nested
        List<String> attrs = searchParam.getAttrs();
        if (Func.isNotEmpty(attrs)) {
            for (String attr : attrs) {
                BoolQueryBuilder query = QueryBuilders.boolQuery();
                // attr = 1_5寸:8寸
                String[] s = attr.split(StringPool.UNDERSCORE);
                String attrId = s[0];
                String[] attrValues = s[1].split(StringPool.COLON);
                query.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                query.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attr", query, ScoreMode.None);
                queryBuilder.filter(nestedQuery);
            }
        }

        sourceBuilder.query(queryBuilder);

        // 2. 排序
        String sort = searchParam.getSort();
        if (Func.isNotEmpty(sort)) {
            // hotScore_desc/asc
            String[] split = sort.split(StringPool.UNDERSCORE);
            sourceBuilder.sort(split[0], SortOrder.fromString(split[1]));
        }
        // 3. 分页
        Integer pageNum = searchParam.getPageNum();
        sourceBuilder.from((pageNum - 1) * EsConstant.PRODUCT_PAGE_SIZE);
        sourceBuilder.size(EsConstant.PRODUCT_PAGE_SIZE);

        /*
        高亮
         */
        if (Func.isNotEmpty(keyword)) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            sourceBuilder.highlighter(highlightBuilder);
        }

        /*
        聚合分析
         */
        TermsAggregationBuilder brandAggregation = AggregationBuilders.terms("brand_agg")
                .field("brandId")
                .size(10)
                .subAggregation(AggregationBuilders.terms("brand_name_agg")
                        .field("brandName")
                        .size(10))
                .subAggregation(AggregationBuilders.terms("brand_img_agg")
                        .field("brandImg")
                        .size(10));

        TermsAggregationBuilder catelogAggregation = AggregationBuilders.terms("catelog_agg")
                .field("catalogId")
                .size(10)
                .subAggregation(AggregationBuilders.terms("catalog_name_agg")
                        .field("catalogName")
                        .size(10));

        NestedAggregationBuilder attrAggregation = AggregationBuilders.nested("attr_agg", "attrs")
                .subAggregation(AggregationBuilders.terms("attr_id_agg")
                        .field("attrs.attrId")
                        .size(10)
                        .subAggregation(AggregationBuilders.terms("attr_name_agg")
                                .field("attrs.attName")
                                .size(10))
                        .subAggregation(AggregationBuilders.terms("attr_value_agg")
                                .field("attrs.attValue")
                                .size(10))
                );

        sourceBuilder.aggregation(brandAggregation);
        sourceBuilder.aggregation(catelogAggregation);
        sourceBuilder.aggregation(attrAggregation);

        log.info("构建的DSL" + sourceBuilder);

        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
        log.info("search:{}", searchRequest);
        return searchRequest;
    }

}
